<template>
  <div class="service-worker-test-event">
    <h1>{{ message }}</h1>
    <section>
        <h2>一、我们先看一下它的几个状态都是什么意思</h2>
        <ol>
            <li>
                installing：这个状态发生在 Service Worker 注册之后，表示worker开始安装，会触发worker中的install事件
            </li>
            <li>
                installed：worker安装成功,如果worker中有event.waitUntil(new Promise())，说明这个的promise成功，可以做一些离线文件的缓存
            </li>
            <li>
                activating： 开始激活，安装成功后，如果是第一次安装会直接进入该状态，如果是新替换旧，需要skipWaiting()以后进入该状态,去触发worker中的activate回调
            </li>
            <li>
                activated：激活完毕状态，可以正常对页面进行控制,如果worker中有event.waitUntil(new Promise())，说明这个的promise成功，可以对比新老缓存数据，进行筛选
            </li>
            <li>
                redundant：安装失败状态,如果worker中有event.waitUntil(new Promise())，说明这个的promise失败
            </li>
        </ol>
        <h4>e.state 返回的是每种状态</h4>
        <code>
            <pre>
                navigator.serviceWorker.register('./sevice.worker.js')
                .then(registration => {
                    // installing, installed, activating, activated, or redundant.
                    registration.onupdatefound = function() {
                        var e = registration.installing as ServiceWorker;
                        e.onstatechange = function() {
                            console.error(e.state);
                            switch (e.state) {
                                case "installed":
                                    console.log("New or updated content is available.");
                                    break;
                                case "redundant":
                                    console.error("The installing service worker became redundant.")
                            }
                        }
                    }
                });
            </pre>
        </code>
    </section>
    <section>
        <h2>二、service worker 所有支持的事件</h2>
        <ol>
            <li>install：监听开始安装的事件</li>
            <li>activate：监听激活完成的事件</li>
            <li>message：监听逻辑页面给worker线程发送的消息事件</li>
            <li>fetch：监听所有的fetch事件</li>
            <li>sync：后台同步</li>
            <li>push：监听push推送事件</li>
            <li>online/offline：监听网络变化</li>
        </ol>
        <h3>1、install 和 activate 结合第一部分中的代码可以清楚看到worker每一个状态</h3>
        <code>
            <pre>
                self.addEventListener('install', function(event) {
                    console.error('installing');
                    event.waitUntil(new Promise(function(resolve) {
                        // 可以做一些重要数据缓存
                        setTimeout(() => {
                            console.log('resolve 5s')
                            resolve()
                        }, 2000);
                    }));
                });
                self.addEventListener('activate', function (event) {
                    console.error('activate');
                    event.waitUntil(new Promise(function(resolve) {
                        // 可以做新老数据的筛选
                        setTimeout(() => {
                            console.log('resolve 5s')
                            resolve()
                        }, 4000);
                    }));
                });
            </pre>
        </code>
        <h4>输出结果如下</h4>
        <img src="../../images/somethingInterssting/worker/stage.jpg" class="img-shadow"/>
        <h3>2、message</h3>
        <p>监听js 线程发来的消息，可以根据页面给的相应状态做相应处理，例如强制更新worker，这样在woker 更新时不用等到释放所有的页面</p>
        <code>
            <pre>
                // worker
                self.addEventListener('message', event => { 
                    // 直接跳过等待状态，直接接管页面
                    if (event.data === 'skip') {
                        self.skipWaiting();
                    }
                });

                // js 主线程
                (navigator.serviceWorker.controller as ServiceWorker).postMessage('skip');
            </pre>
        </code>
        <h3>2、fetch</h3>
        <p>监听整个站点的fetch，我们试一下，尽量不要对所有的fetch都做拦截，消耗巨大</p>
        <div class="flex">
            <div class="left">
                <div>
                    <p>根据拦截到的信息做不同的处理</p>
                    <span class="btn" @click="fetchTest">点击我</span> 
                </div>
            </div><div class="right">
                返回值：
                {{feedback}}
            </div>
        </div>
        <code>
            <pre>
                // worker.js
                self.addEventListener('fetch', event => {
                    // 可以结合cache 做一些缓存处理
                    // 自定义返回值
                    // 注意特别耗性能，谨慎处理
                    console.error(event.request.url);
                    if (new URL(event.request.url).search.includes('intercept')){
                        event.respondWith(new Promise(resolve => {
                            if (new URL(event.request.url).search.includes('count')) {
                                fetch(event.request).then(function(response) {
                                    console.log('Response from network is:', response);
                                    return resolve(response);
                                });
                            } else if (new URL(event.request.url).search.includes('intercept')) {
                                resolve(new Response('Hello World!'));
                            }
                        }));
                    }
                });

                // 页面逻辑
                fetchTest ():void {
                // fetch 默认不带cookies
                let count = this.count % 2 ? `&count=11` : '';
                this.count += 1;
                let myHeaders = new Headers({
                    'Access-Control-Allow-Origin': '*',
                    'Content-Type': 'text/plain'
                });
                fetch(`/web.worker.js?intercept=true${count}`, {
                    method: 'GET',
                    credentials: 'include',
                    mode: 'cors',
                    headers: myHeaders,
                }).then(response => response.text())
                .then(res => this.feedback = res)
                .catch(e => console.log("Oops, error", e));
            }
            </pre>
        </code>
        <h3>3、sync</h3>
        <p>离线处理，更好解决没网的条件下，一些请求发送成功性问题。只有在有网的时候才会触发它，当你网络重联时会自动触发，兼容性不好</p>
        <p>大致流程如下 <a href="https://www.jianshu.com/p/10d3e99742ca">图片参考文章</a></p>
        <img src="../../images/somethingInterssting/worker/sync.jpg" class="img-shadow"/>
        <ul>
            <li>首先，你需要在Service Worker中监听sync事件；</li>
            <li>然后，在浏览器中发起后台同步sync（图中第一步）；</li>
            <li>之后，会触发Service Worker的sync事件，在该监听的回调中进行操作，例如向后端发起请求（图中第二步）</li>
            <li>最后，可以在Service Worker中对服务端返回的数据进行处理</li>
        </ul>
        <h4>大概示例代码如下</h4>
        <code>
            <pre>
                navigator.serviceWorker.ready.then(function (registration) {
                    var tag = "dai";
                    registration.sync.register(tag).then(function (e) {
                        console.error('后台同步已触发', tag);
                    }).catch(function (err) {
                        console.error('后台同步触发失败', err);
                    });
                });

                // worker.js
                self.addEventListener('sync', function (e) {
                    if (e.tag == 'dai') {
                        e.waitUntil(new Promise(res => {
                            fetch('/web.worker.js').then(function (response) {
                                console.error(response);
                                // doSomething
                            })
                        }));
                    }
                });
            </pre>
        </code>
        <p>在断网情况下不会触发sync，去请求数据，连上网以后马上回去进行请求，测试时候可直接断开无线或者网线，chrome 自带的offline不准确</p>
        <h3>4、push</h3>
        <p>web push走的是谷歌FCM通道，需要能接收到谷歌服务器的消息，国内不太好用，event 是PushEvent包含push的一些信息，里面data,可以带服务推过来的信息</p>
        <p><a href="https://firebase.google.cn/docs/cloud-messaging/concept-options?hl=zh-cn">FCM穿梭门</a>可能需要翻墙</p>
        <p>例子</p>
        <code>
            <pre>
                self.addEventListener('push', function(event) {
                    self.registration.showNotification("Hi：", {
                        body: '我收到了push',
                        icon: 'http://localhost:8080/images/tangcusuan.a548a.png',
                        tag: 'push'
                    });
                });
            </pre>
        </code>
        <p>例子是收到push后，给用户一个notification提示，所以得到以下结果（此例为mac环境，win10应该是在右下角弹出相应提示），前提浏览器的notification必须是允许状态　</p>
        <img src="../../images/somethingInterssting/worker/servicepush.jpg" class="img-shadow"/>
        <p>在国内不能翻墙的话，亦可以通过devtools,手动触发push，在Application下，找到下图位置的push 按钮就可以发送模拟push，同时也可以测sync等功能。</p>
        <img src="../../images/somethingInterssting/worker/pushdev.jpg" class="img-shadow"/>
    </section>
    <section>
        <h2>三、其他</h2>
        <p>在我们开发service worker 过程中，难免会频繁更新service worker文件，我们会发现直接更新worker 文件不生效，甚至刷新页面也不会生效新的worker逻辑，而是会出现以下状态</p>
        <img src="../../images/somethingInterssting/worker/skipwaiting.jpg" class="img-shadow"/>
        <p>这时候需要我们手动去点update或者skipWaiting,才能让新的worker接管页面。或者关掉所有接管的页面，但是实际中，开发者无法控制用户的关闭操作，这时候我们可以通过以下操作，直接让新的worker,结果整个页面</p>
        <code>
            <pre>
                // worker.js 
                self.addEventListener('install', event => {
                    // 直接跳过等待状，直接接管页面
                    event.waitUntil(self.skipWaiting());
                });

                // 页面
                // 避免新老版本替换时，一部分老版本控制，一部分新版本控制发生问题
                navigator.serviceWorker.addEventListener('controllerchange', () => {
                    window.location.reload();
                });
                
                // 或当检测到变化时，提醒用户，等用户同意，我们在告诉worker 去更新
                //(navigator.serviceWorker.controller as ServiceWorker).postMessage('skip');
            </pre>
        </code>
    </section>
    <p class="page-end">The End</p>
  </div>
</template>

<script lang="ts">
    import Vue from 'vue';
    import Component from 'vue-class-component';
    @Component({
        // 所有的组件选项都可以放在这里
    })
    export default class service extends Vue {
        // data 在这
        message: string = 'Service Worker相关'

        count: number = 1
        feedback: string = ''
        fetchTest ():void {
            // fetch 默认不带cookies
            let count = this.count % 2 ? `&count=11` : '';
            this.count += 1;
            let myHeaders = new Headers({
                'Access-Control-Allow-Origin': '*',
                'Content-Type': 'text/plain'
            });
            fetch(`/web.worker.js?intercept=true${count}`, {
                method: 'GET',
                credentials: 'include',
                mode: 'cors',
                headers: myHeaders,
            }).then(response => {
                return response.text();
            })
            .then(res => {
                this.feedback = res;
            })
            .catch(e => {
                console.log("Oops, error", e);
            });
        }
        created () {
			navigator.serviceWorker.register('./sevice.worker.js')
			.then(registration => {
                // installing, installed, activating, activated, or redundant.
                registration.onupdatefound = function() {
                    var e = registration.installing as ServiceWorker;
                    e.onstatechange = function() {
                        console.error(e.state);
                        switch (e.state) {
                            case "installed":
                                console.log("New or updated content is available.");
                                break;
                            case "redundant":
                                console.error("The installing service worker became redundant.")
                        }
                    }
                }
            })
			.catch(err => console.error('ServiceWorker 注册失败: ', err));
			navigator.serviceWorker.addEventListener('message',data => {
				console.error('result:',data);
            });
            
            // 避免新老版本替换时，一部分老版本控制，一部分新版本控制发生问题
            let refreshing = false;
            navigator.serviceWorker.addEventListener('controllerchange', () => {
                // 避免可能发生的无限刷新
                if (refreshing) {
                    return
                }
                refreshing = true;
                window.location.reload();
            });

            (navigator.serviceWorker.controller as ServiceWorker).postMessage('skip');

            // index.js
            navigator.serviceWorker.ready.then(function (registration) {
                var tag = "dai";
                registration.sync.register(tag).then(function (e) {
                    console.error('后台同步已触发', tag);
                }).catch(function (err) {
                    console.error('后台同步触发失败', err);
                });
            });
        }
    }
</script>
<style lang="scss">
.service-worker-test-event{
    .flex{
        margin-bottom: 20px;
    }
    .left{
        display: flex;
        justify-content: center;
        align-items: center;
        background: #f4f4f4;
        div{
            text-align: center;
        }
    }
    .right{
		padding: 20px;
		text-align: center;
		font-size: 16px;
		font-weight: 800;
	}
}
</style>